﻿using Mapster;
using System.Collections.Concurrent;

namespace Demo.PetaPoco.Application;

/// <summary>
/// 基于 Mapster 的通用对象映射扩展方法集合
/// </summary>
public static class MapsterExtensions
{
	/// <summary>
	/// 存储映射配置的字典，键为 (源类型, 目标类型) 的元组，值为对应的 TypeAdapterConfig
	/// 使用 ConcurrentDictionary 以支持线程安全
	/// </summary>
	private static readonly ConcurrentDictionary<(Type Source, Type Destination), TypeAdapterConfig> ConfigCache
		= new ConcurrentDictionary<(Type, Type), TypeAdapterConfig>();

	/// <summary>
	/// 将源对象映射为目标类型的对象。
	/// 如果映射配置不存在，则根据提供的配置自动注册；如果已存在，则直接使用缓存的配置。
	/// </summary>
	/// <typeparam name="TSource">源对象类型</typeparam>
	/// <typeparam name="TDestination">目标对象类型</typeparam>
	/// <param name="source">源对象</param>
	/// <param name="configure">映射规则配置委托，如果为 null 则使用默认映射</param>
	/// <returns>映射后的目标对象</returns>
	public static TDestination MapTo<TSource, TDestination>(
		this TSource source,
		Action<TypeAdapterSetter<TSource, TDestination>>? configure = null)
		where TDestination : class
		where TSource : class
	{
		return MapToInternal(source, default, configure);
	}

	/// <summary>
	/// 将源对象映射覆盖到现有的目标对象。
	/// 如果映射配置不存在，则根据提供的配置自动注册；如果已存在，则直接使用缓存的配置。
	/// </summary>
	/// <typeparam name="TSource">源对象类型</typeparam>
	/// <typeparam name="TDestination">目标对象类型</typeparam>
	/// <param name="source">源对象</param>
	/// <param name="destination">目标对象，将被源对象的映射结果覆盖</param>
	/// <param name="configure">映射规则配置委托，如果为 null 则使用默认映射</param>
	/// <returns>被覆盖后的目标对象</returns>
	public static TDestination MapTo<TSource, TDestination>(
		this TSource source,
		TDestination destination,
		Action<TypeAdapterSetter<TSource, TDestination>>? configure = null)
		where TDestination : class
		where TSource : class
	{
		if (destination == null)
			throw new ArgumentNullException(nameof(destination));

		return MapToInternal(source, destination, configure);
	}

	/// <summary>
	/// 内部方法，用于处理映射逻辑，合并两个 MapTo 重载的公共代码
	/// </summary>
	/// <typeparam name="TSource">源对象类型</typeparam>
	/// <typeparam name="TDestination">目标对象类型</typeparam>
	/// <param name="source">源对象</param>
	/// <param name="destination">目标对象，如果为 null 则创建新实例</param>
	/// <param name="configure">映射规则配置委托</param>
	/// <returns>映射后的目标对象</returns>
	private static TDestination MapToInternal<TSource, TDestination>(
		TSource source,
		TDestination? destination,
		Action<TypeAdapterSetter<TSource, TDestination>>? configure)
	{
		if (source == null)
			throw new ArgumentNullException(nameof(source));

		// 生成字典的键
		var key = (typeof(TSource), typeof(TDestination));

		// 从缓存获取配置，如果不存在则创建并注册
		var config = ConfigCache.GetOrAdd(key, _ =>
		{
			var newConfig = new TypeAdapterConfig();
			if (configure != null)
			{
				var setter = newConfig.NewConfig<TSource, TDestination>();
				configure(setter);
			}
			return newConfig;
		});

		// 如果提供了配置且缓存中已有配置，检查是否需要更新
		if (configure != null && ConfigCache.ContainsKey(key))
		{
			var updatedConfig = new TypeAdapterConfig();
			var setter = updatedConfig.NewConfig<TSource, TDestination>();
			configure(setter);
			ConfigCache[key] = updatedConfig;
			config = updatedConfig;
		}

		// 根据 destination 是否为 null 选择映射方式
		return destination == null
			? source.Adapt<TDestination>(config)           // 创建新实例
			: source.Adapt(destination, config);           // 覆盖现有实例
	}

	/// <summary>
	/// 清空所有映射配置缓存。
	/// </summary>
	public static void ClearMappings()
	{
		ConfigCache.Clear();
	}
}
